﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toolkit.Web.Resources;

namespace Toolkit.Web.ExtensionMethods
{
    public static class DateTimeExtensions
    {
        /// <summary>
        /// Converts a Coordinated Universal Time (UTC) to the time in a specified time zone.
        /// </summary>
        /// <param name="value">The Coordinated Universal Time (UTC).</param>
        /// <param name="destinationTimeZone">The time zone to convert dateTime to.</param>
        /// <returns>
        /// A System.DateTime value that represents the date and time in the destination
        /// time zone. Its System.DateTime.Kind property is System.DateTimeKind.Utc if
        /// destinationTimeZone is System.TimeZoneInfo.Utc; otherwise, its System.DateTime.Kind
        /// property is System.DateTimeKind.Unspecified.
        /// </returns>
        public static DateTime ToLocalTime(this DateTime value, TimeZoneInfo destinationTimeZone)
        {
            if (destinationTimeZone == null)
            {
                throw new ArgumentNullException("destinationTimeZone");
            }

            return TimeZoneInfo.ConvertTimeFromUtc(value, destinationTimeZone);
        }

        /// <summary>
        /// Converts the time in a specified time zone to Coordinated Universal Time (UTC).
        /// </summary>
        /// <param name="value">The date and time to convert.</param>
        /// <param name="destinationTimeZone">The time zone of dateTime.</param>
        /// <returns>
        /// A System.DateTime object that represents the Coordinated Universal Time (UTC)
        /// that corresponds to the dateTime parameter. The System.DateTime object's
        /// System.DateTime.Kind property is always set to System.DateTimeKind.Utc.
        /// </returns>
        public static DateTime ToUniversalTime(this DateTime value, TimeZoneInfo destinationTimeZone)
        {
            if (destinationTimeZone == null)
            {
                throw new ArgumentNullException("destinationTimeZone");
            }

            return TimeZoneInfo.ConvertTimeToUtc(value, destinationTimeZone);
        }

        /// <summary>
        /// Add (or subtract) a specified number of weekdays.
        /// </summary>
        /// <param name="value">Initial date time to add weekdays to.</param>
        /// <param name="days">Number of weekdays to add.</param>
        /// <returns>New date value.</returns>
        public static DateTime AddWeekdays(this DateTime value, int days)
        {
            var sign = days < 0 ? -1 : 1;
            var unsignedDays = Math.Abs(days);
            var weekdaysAdded = 0;
            while (weekdaysAdded < unsignedDays)
            {
                value = value.AddDays(sign);
                if (value.DayOfWeek != DayOfWeek.Saturday && value.DayOfWeek != DayOfWeek.Sunday)
                {
                    weekdaysAdded += 1;
                }
            }
            return value;
        }

        /// <summary>
        /// Assigns a DateTime variable's hour, minute, second, and/or millisecond settings.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="hour"></param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime value, int hour)
        {
            return value.SetTime(hour, 0, 0, 0);
        }

        /// <summary>
        /// Assigns a DateTime variable's hour, minute, second, and/or millisecond settings.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="hour"></param>
        /// <param name="minute"></param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime value, int hour, int minute)
        {
            return value.SetTime(hour, minute, 0, 0);
        }

        /// <summary>
        /// Assigns a DateTime variable's hour, minute, second, and/or millisecond settings.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="hour"></param>
        /// <param name="minute"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime value, int hour, int minute, int second)
        {
            return value.SetTime(hour, minute, second, 0);
        }

        /// <summary>
        /// Assigns a DateTime variable's hour, minute, second, and/or millisecond settings.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="hour"></param>
        /// <param name="minute"></param>
        /// <param name="second"></param>
        /// <param name="millisecond"></param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime value, int hour, int minute, int second, int millisecond)
        {
            return new DateTime(value.Year, value.Month, value.Day, hour, minute, second, millisecond);
        }

        /// <summary>
        /// Determines the first day of the month associated with the year and month of the current instance DateTime.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DateTime FirstDayOfMonth(this DateTime value)
        {
            return new DateTime(value.Year, value.Month, 1);
        }

        /// <summary>
        /// Determines the last day of the month associated with the year and month of the current instance DateTime.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DateTime LastDayOfMonth(this DateTime value)
        {
            return new DateTime(value.Year, value.Month, DateTime.DaysInMonth(value.Year, value.Month));
        }

        /// <summary>
        /// Generates a relative and "human readable" date string in association with the current date and time.
        /// </summary>
        /// <param name="value">DateTime in UTC format.</param>
        /// <returns>DateTime in relative format.</returns>
        public static string ToRelativeUniversalTimeDateString(this DateTime value)
        {
            return GetRelativeDateValue(value, DateTime.Now);
        }

        /// <summary>
        /// Generates a relative and "human readable" date string in association with the current date and time.
        /// </summary>
        /// <param name="value">DateTime in local time zone format.</param>
        /// <returns>DateTime in relative format.</returns>
        public static string ToRelativeLocalTimeDateString(this DateTime value)
        {
            return GetRelativeDateValue(value, DateTime.UtcNow);
        }

        /// <summary>
        /// Generates a relative and "human readable" date string in association with the current date and time.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static string GetRelativeDateValue(DateTime dateTime, DateTime compareToDateTime)
        {
            TimeSpan diff = compareToDateTime.Subtract(dateTime);

            if (diff.TotalDays >= 365)
            {
                return string.Format(Language.DateTimeExtensions_on_Format, dateTime.ToString("MMMM d, yyyy"));
            }
            else if (diff.TotalDays >= 7)
            {
                return string.Format(Language.DateTimeExtensions_on_Format, dateTime.ToString("MMMM d"));
            }
            else if (diff.TotalDays > 1)
            {
                return string.Format(Language.DateTimeExtensions_daysAgo_Format, diff.TotalDays);
            }
            else if (diff.TotalDays == 1)
            {
                return Language.DateTimeExtensions_yesterday;
            }
            else if (diff.TotalHours >= 2)
            {
                return string.Format(Language.DateTimeExtensions_hoursAgo_Format, diff.TotalHours);
            }
            else if (diff.TotalMinutes >= 60)
            {
                return Language.DateTimeExtensions_moreThanAnHourAgo;
            }
            else if (diff.TotalMinutes >= 5)
            {
                return string.Format(Language.DateTimeExtensions_minutesAgo_Format, diff.TotalMinutes);
            }
            else if (diff.TotalMinutes >= 1)
            {
                return Language.DateTimeExtensions_aFewMinutesAgo;
            }
            else
            {
                return Language.DateTimeExtensions_lessThanAMinuteAgo;
            }
        }
    }
}
